/* Complet (Autocompletion through Graph Databases)
 *  Copyright (C) 2012  Paul Wagner, Till Speicher
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 3 of the License, or (at your
 *  option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, see <http:/* www.gnu.org/licenses/>.
 */

/*
 * Class for evaluating a text
 *
 * @author Paul Wagner
 */
package complet.db;

import complet.helper.ConfigHelper;
import complet.helper.IOHelper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeMap;

public class Evaluator {

    private int anz_stnce = 1000; // Standard values. Change on runtime using setter()
    private boolean random_stnce = true;  // Using random words out of a long sentence. Disable if you need comparable results for every eval()
    private BufferedReader in = null;
    protected Retrieval ret = null;
    private BufferedWriter evalLogFile = IOHelper.openWriteFile("Evaluation.log");
    private BufferedWriter writeWordStatistic1 = null; // 1 given letter
    private BufferedWriter writeWordStatistic2 = null; // 2 given letter
    private BufferedWriter writeWordStatistic3 = null; // 3 given letter
    private TreeMap<String, Integer> wS1 = new TreeMap<String, Integer>(); // Anzahl der Treffer für die Worte
    private TreeMap<String, Integer> wS2 = new TreeMap<String, Integer>();
    private TreeMap<String, Integer> wS3 = new TreeMap<String, Integer>();
    private TreeMap<String, Integer> v_wS1 = new TreeMap<String, Integer>(); // Anzahl der Gesamtvorschläge der Wörter
    private TreeMap<String, Integer> v_wS2 = new TreeMap<String, Integer>();
    private TreeMap<String, Integer> v_wS3 = new TreeMap<String, Integer>();
    private TreeMap<String, Integer> w = new TreeMap<String, Integer>(); // Anzahl der Gesamtvorkommen der Wörter
    private TreeMap<String, Integer> s_wS1, s_wS2, s_wS3;
    protected int cnt_stnce = 0;          // overall sentence-counter, for 1 letter
    protected int cnt_stnce_2 = 0;          // overall sentence-counter, for 2 letters
    protected int cnt_stnce_3 = 0;          // overall sentence-counter, for 3 letters
    protected int sum_pos_1 = 0;           // Just for loc!
    protected int sum_pos_2 = 0;           // Just for loc!
    protected int sum_pos_3 = 0;           // Just for loc!
    protected int[] cnt_0;   // How many best(1) or top-ten(2) hits with no given letter
    protected int[] cnt_1;   // How many best(1) or top-ten(2) hits with 1 given letter
    protected int[] cnt_2;   // How many best(1) or top-ten(2) hits with 2 given letters
    protected int[] cnt_3;   // How many best(1) or top-ten(2) hits with 3 given letters
    protected int[] loc_0;   // How many best(1) or top-ten(2) hits with no given letter
    protected int[] loc_1;   // How many best(1) or top-ten(2) hits with 1 given letter
    protected int[] loc_2;   // How many best(1) or top-ten(2) hits with 2 given letters
    protected int[] loc_3;   // How many best(1) or top-ten(2) hits with 3 given letters
    protected double[] cnt_dcg; //Discounted cumulative gain
    protected double[] cnt_cg;  //Counted cumulative gain
    protected double[] loc_dcg; //Discounted cumulative gain
    protected double[] loc_cg;  //Counted cumulative gain

    /*
     * Constructor using ConfigHelper, buffer database
     */
    public Evaluator() {
        this(ConfigHelper.DB_PATH, ConfigHelper.IN_PATH, ConfigHelper.MAX_RELATIONS, ConfigHelper.NAME_KEY, ConfigHelper.COUNT_KEY, ConfigHelper.CACHE_TYPE, ConfigHelper.BUFFER);
    }

    /*
     * Constructor using manual config values
     *
     */
    public Evaluator(String database, String in_path, int max_relations, String name_key, String count_key, String cache_type, Boolean buffer) {
        // Establish connection
        IOHelper.log("(Evaluator) New Evaluator");
        ret = new Retrieval(database, max_relations, name_key, count_key, cache_type, buffer);

        // Setup input reader
        in = IOHelper.openReadFile(in_path);

        // Instanciate sortet stat map
        s_wS1 = new TreeMap<String, Integer>(COMP_STAT1);
        s_wS2 = new TreeMap<String, Integer>(COMP_STAT2);
        s_wS3 = new TreeMap<String, Integer>(COMP_STAT3);
    }

    /*
     * Constructor without DBConnection for super classes
     */
    public Evaluator(String database, String in_path, int max_relations, String name_key, String count_key, String cache_type, Boolean buffer, Boolean connect) {
        if (connect) {
            // Establish connection
            IOHelper.log("(Evaluator) New Evaluator");
            ret = new Retrieval(database, max_relations, name_key, count_key, cache_type, buffer);
        }
        // Setup input reader
        in = IOHelper.openReadFile(in_path);
    }
    // COMPARATORS
    private final Comparator<String> COMP_STAT1 = new Comparator<String>() {

        public int compare(String s1, String s2) {
            if (wS1.get(s1) > wS1.get(s2)) {
                return -1;
            }
            if (wS1.get(s1) < wS1.get(s2)) {
                return 1;
            }
            int _return = s1.compareTo(s2);
            if (_return != 0) {
                return _return;
            }
            return 1;
        }
    };
    private final Comparator<String> COMP_STAT2 = new Comparator<String>() {

        public int compare(String s1, String s2) {
            if (wS2.get(s1) > wS2.get(s2)) {
                return -1;
            }
            if (wS2.get(s1) < wS2.get(s2)) {
                return 1;
            }
            int _return = s1.compareTo(s2);
            if (_return != 0) {
                return _return;
            }
            return 1;
        }
    };
    private final Comparator<String> COMP_STAT3 = new Comparator<String>() {

        public int compare(String s1, String s2) {
            if (wS3.get(s1) > wS3.get(s2)) {
                return -1;
            }
            if (wS3.get(s1) < wS3.get(s2)) {
                return 1;
            }
            int _return = s1.compareTo(s2);
            if (_return != 0) {
                return _return;
            }
            return 1;
        }
    };

    // GETTER AND SETTER
    public int getAnz_stnce() {
        return anz_stnce;
    }

    public void setAnz_stnce(int anz_stnce) {
        this.anz_stnce = anz_stnce;
    }

    public int[] getCnt_0() {
        return cnt_0;
    }

    public int[] getCnt_1() {
        return cnt_1;
    }

    public int[] getCnt_2() {
        return cnt_2;
    }

    public int[] getCnt_3() {
        return cnt_3;
    }

    public int[] getLoc_0() {
        return loc_0;
    }

    public int[] getLoc_1() {
        return loc_1;
    }

    public int[] getLoc_2() {
        return loc_2;
    }

    public int[] getLoc_3() {
        return loc_3;
    }

    public void setP1(int p) {
        if (ret != null) {
            ret.setP1(p);
        }
    }

    public void setP2(int p) {
        if (ret != null) {
            ret.setP2(p);
        }
    }

    public void setP3(int p) {
        if (ret != null) {
            ret.setP3(p);
        }
    }

    public void setP4(int p) {
        if (ret != null) {
            ret.setP4(p);
        }
    }

    public double[] getCnt_cg() {
        return cnt_cg;
    }

    public double[] getCnt_dcg() {
        return cnt_dcg;
    }

    public double[] getLoc_cg() {
        return loc_cg;
    }

    public double[] getLoc_dcg() {
        return loc_dcg;
    }

    public void setRandom_stnce(boolean rand) {
        random_stnce = rand;
    }

    public double getAvgPos1() {
        if (cnt_stnce > 0) {
            return ((double) (sum_pos_1)) / cnt_stnce;
        }
        return -1;
    }

    public double getAvgPos2() {
        if (cnt_stnce_2 > 0) {
            return ((double) (sum_pos_2)) / cnt_stnce_2;
        }
        return -1;
    }

    public double getAvgPos3() {
        if (cnt_stnce_3 > 0) {
            return ((double) (sum_pos_3)) / cnt_stnce_3;
        }
        return -1;
    }

    // METHODS

    /*
     * Doing Evaluation
     *
     */
    public void eval() {
        IOHelper.log("(Evaluator) Start evaluation with params " + ret.getP1() + ", " + ret.getP2() + ", " + ret.getP3() + ", " + ret.getP4());
        wS1.clear();
        wS2.clear();
        wS3.clear();
        w.clear();
        v_wS1.clear();
        v_wS2.clear();
        v_wS3.clear();
        cnt_0 = new int[2];
        cnt_1 = new int[2];
        cnt_2 = new int[2];
        cnt_3 = new int[2];
        loc_0 = new int[2];
        loc_1 = new int[2];
        loc_2 = new int[2];
        loc_3 = new int[2];
        cnt_cg = new double[3];
        cnt_dcg = new double[3];
        loc_cg = new double[3];
        loc_dcg = new double[3];
        writeWordStatistic1 = IOHelper.openWriteFile("WordStat_1.log"); // 1 given letter
        writeWordStatistic2 = IOHelper.openWriteFile("WordStat_2.log"); // 2 given letter
        writeWordStatistic3 = IOHelper.openWriteFile("WordStat_3.log"); // 3 given letter
        cnt_stnce = 0;
        cnt_stnce_2 = 0;
        cnt_stnce_3 = 0;
        sum_pos_1 = 0;
        sum_pos_2 = 0;
        sum_pos_3 = 0;
        //cnt_pos = 0;
        String org_word = "";
        String stnce = "";
        Double t;


        while ((stnce = getNextLine()) != null) {
            cnt_stnce++;

            if (cnt_stnce % 10000 == 9999) {
                IOHelper.log("####### (Evaluator) RESULTS - STILL IN PROGRESS... ########");
                logResults();
            }


            //String[] words = stnce.split(" ");                    // NO LOWERCASE
            String[] words = stnce.toLowerCase().split(" ");        // LOWERCASE

            if (words.length >= 5) {
                String[] input = new String[4];
                int ran = 0;
                if (random_stnce) {
                    ran = (int) (Math.random() * words.length) - 4;
                    if (ran < 0) {
                        ran = 0;
                    }
                }
                System.arraycopy(words, ran, input, 0, 4);
                org_word = words[ran + 4];
                ret.setSentence(input, org_word.substring(0, 1));
            } else {
                continue;
            }

            // Make retrieval
            IOHelper.pln("Lookup sentence " + cnt_stnce);
            t = ret.eval();

            // Evaluating Result
            //TreeMap<String, Word> res = ret.getResult();
            Set<String> set;

            for (int i = 1; i <= 1; i++) {
                set = ret.getSortedKeySet(i);
                evalResult(set, org_word, stnce, i, t);
            }
        }

        // Write Statistics
        writeStatistic();

        IOHelper.log("####### (Evaluator) RESULTS - FINISHED... ########");
        logResults();
    }


    /*
     * Load input file to RAM
     *
     */
    protected String getNextLine() {
        try {
            String strLine = in.readLine();
            strLine = strLine.replaceAll("ß", "ss");
            strLine = strLine.replaceAll("ü", "ue");
            strLine = strLine.replaceAll("ä", "ae");
            strLine = strLine.replaceAll("ö", "oe");
            strLine = strLine.replaceAll("Ü", "ue");
            strLine = strLine.replaceAll("Ä", "ae");
            strLine = strLine.replaceAll("Ö", "oe");
            strLine = strLine.toLowerCase();
            strLine = strLine.replaceAll("[^a-z ]", "");
            if (cnt_stnce >= anz_stnce && anz_stnce != -1) {
                return null;
            }
            return strLine;
        } catch (IOException ex) {
            IOHelper.logError("(Evaluator.getNextLine()) Something went wrong loading next line of input file: " + ex.getMessage());
            return null;
        }
    }

    private void evalResult(Set<String> set, String org_word, String stnce, int s, double t) {
        int c;
        int pos1 = -1;
        int pos2 = -1;
        int pos3 = -1;
        String best1 = "";
        String best2 = "";
        String best3 = "";
        String _s;
        int[] t_1;
        int[] t_2;
        int[] t_3;
        double[] cg;
        double[] dcg;

        switch (s) {
            case 0:
                t_1 = cnt_1;
                t_2 = cnt_2;
                t_3 = cnt_3;
                cg = cnt_cg;
                dcg = cnt_dcg;
                _s = "cnt";
                break;
            case 1:
                t_1 = loc_1;
                t_2 = loc_2;
                t_3 = loc_3;
                cg = loc_cg;
                dcg = loc_dcg;
                _s = "loc";
                break;
            default:
                return;
        }

        //1. anfangsbuchstabe vorgegeben
        c = 0;
        for (String key : set) {
            if (c++ >= 5) {
                break;
            }
            if (key.equals(org_word)) {
                //IOHelper.pln(_s + ":1 - Found a hit: " + key);
                pos1 = c;
                if (c == 1) {
                    t_1[0]++;
                } else {
                    t_1[1]++;
                }
            }
            if (c == 1) {
                best1 = key;
            }
        }
        if (pos1 > -1) {
            cg[0] += 1d / pos1;
            dcg[0] += 1d / Math.log(pos1 + 0.1);
        }

        // Durchlaufen für die Positionsbestimmung
        int c1 = 0;
        int c2 = 0;
        int c3 = 0;
        boolean isin = false;
        if (_s.equals("loc")) {
            for (String key : set) {
                // Wenn für Buchstaben passt, counter hoch
                if (org_word.substring(0, 1).equals(key.substring(0, 1))) {
                    c1++;
                }
                if (org_word.length() >= 3 && key.length() >= 3) {
                    if (org_word.substring(0, 2).equals(key.substring(0, 2))) {
                        c2++;
                    }
                }
                if (org_word.length() >= 4 && key.length() >= 4) {
                    if (org_word.substring(0, 3).equals(key.substring(0, 3))) {
                        c3++;
                    }
                }
                // Wenn richtiges Wort da, raus
                if (org_word.equals(key)) {
                    isin = true;
                    break;
                }
            }
            if (isin) {
                sum_pos_1 += c1;
                sum_pos_2 += c2;
                sum_pos_3 += c3;
            }
        }



        //2. anfangsbuchstabe vorgegeben
        if (org_word.length() >= 3) {
            if (_s.equals("loc")) {
                cnt_stnce_2++;
            }
            c = 0;
            for (String key : set) {
                if (key.length() < 2) {
                    continue;
                }
                if (!key.substring(0, 2).equals(org_word.substring(0, 2))) {
                    continue;
                }
                if (c++ >= 5) {
                    break;
                }
                if (key.equals(org_word)) {
                    //IOHelper.pln(_s + ":2 - Found a hit: " + key);
                    pos2 = c;
                    if (c == 1) {
                        t_2[0]++;
                    } else {
                        t_2[1]++;
                    }
                }
                if (c == 1) {
                    best2 = key;
                }
            }
            if (pos2 > -1) {
                cg[1] += 1d / pos2;
                dcg[1] += 1d / Math.log(pos2 + 0.1);
            }
        }

        //3. anfangsbuchstabe vorgegeben
        if (org_word.length() >= 4) {
            if (_s.equals("loc")) {
                cnt_stnce_3++;
            }
            c = 0;
            for (String key : set) {
                if (key.length() < 3) {
                    continue;
                }
                if (!key.substring(0, 3).equals(org_word.substring(0, 3))) {
                    continue;
                }
                if (c++ >= 5) {
                    break;
                }
                if (key.equals(org_word)) {
                    //IOHelper.pln(_s + ":3 - Found a hit: " + key);
                    pos3 = c;
                    if (c == 1) {
                        t_3[0]++;
                    } else {
                        t_3[1]++;
                    }
                }
                if (c == 1) {
                    best3 = key;
                }
            }
            if (pos3 > -1) {
                cg[2] += 1d / pos3;
                dcg[2] += 1d / Math.log(pos3 + 0.1);
            }
        }

        logEvaluation(stnce, org_word, best1, best2, best3, pos1, pos2, pos3, _s, t);
    }

    /*
     * Log current results
     *
     */
    protected void logResults() {
        IOHelper.log("Tested sentences 1: " + cnt_stnce);
        IOHelper.log("Tested sentences 2: " + cnt_stnce_2);
        IOHelper.log("Tested sentences 3: " + cnt_stnce_3);
        IOHelper.log("cnt: 1 letter - first hit: " + cnt_1[0]);
        IOHelper.log("cnt: 1 letter - top five: " + cnt_1[1]);
        IOHelper.log("cnt: 1 letter - cg: " + cnt_cg[0]);
        IOHelper.log("cnt: 1 letter - dcg: " + cnt_dcg[0]);
        IOHelper.log("cnt: 2 letter - first hit: " + cnt_2[0]);
        IOHelper.log("cnt: 2 letter - top five: " + cnt_2[1]);
        IOHelper.log("cnt: 2 letter - cg: " + cnt_cg[1]);
        IOHelper.log("cnt: 2 letter - dcg: " + cnt_dcg[1]);
        IOHelper.log("cnt: 3 letter - first hit: " + cnt_3[0]);
        IOHelper.log("cnt: 3 letter - top five: " + cnt_3[1]);
        IOHelper.log("cnt: 3 letter - cg: " + cnt_cg[2]);
        IOHelper.log("cnt: 3 letter - dcg: " + cnt_dcg[2]);
        IOHelper.log("---");
        IOHelper.log("loc: 1 letter - first hit: " + loc_1[0]);
        IOHelper.log("loc: 1 letter - top five: " + loc_1[1]);
        IOHelper.log("loc: 1 letter - cg: " + loc_cg[0]);
        IOHelper.log("loc: 1 letter - dcg: " + loc_dcg[0]);
        IOHelper.log("loc: 1 letter - avg_pos: " + getAvgPos1());
        IOHelper.log("loc: 2 letter - first hit: " + loc_2[0]);
        IOHelper.log("loc: 2 letter - top five: " + loc_2[1]);
        IOHelper.log("loc: 2 letter - cg: " + loc_cg[1]);
        IOHelper.log("loc: 2 letter - dcg: " + loc_dcg[1]);
        IOHelper.log("loc: 2 letter - avg_pos: " + getAvgPos2());
        IOHelper.log("loc: 3 letter - first hit: " + loc_3[0]);
        IOHelper.log("loc: 3 letter - top five: " + loc_3[1]);
        IOHelper.log("loc: 3 letter - cg: " + loc_cg[2]);
        IOHelper.log("loc: 3 letter - dcg: " + loc_dcg[2]);
        IOHelper.log("loc: 3 letter - avg_pos: " + getAvgPos3());
        IOHelper.log("---");
        if (cnt_stnce != 0) {
            IOHelper.log("cnt overall points: " + ((cnt_0[0] * 10 + cnt_1[0] * 7 + cnt_2[0] * 5 + cnt_3[0] * 3) + (cnt_0[1] * 7 + cnt_1[1] * 4 + cnt_2[1] * 2 + cnt_3[1])));
            IOHelper.log("loc overall points: " + ((loc_0[0] * 10 + loc_1[0] * 7 + loc_2[0] * 5 + loc_3[0] * 3) + (loc_0[1] * 7 + loc_1[1] * 4 + loc_2[1] * 2 + loc_3[1])));
        }
    }

    /*
     * log evaluation
     *
     */
    private void logEvaluation(String stnce, String org_word, String best1, String best2, String best3, int pos1, int pos2, int pos3, String s, Double t) {
        // Log Statistic
        if (s.equals("loc")) {
            int c = 0;
            if (w.containsKey(org_word)) {
                c = w.get(org_word);
            }
            w.put(org_word, c + 1);

            for (int i = 1; i <= 3; i++) {
                int cnt = 0;
                int pos = 0;
                String b = "";
                TreeMap<String, Integer> tm = null;
                TreeMap<String, Integer> tm2 = null;
                switch (i) {
                    case 1:
                        tm = wS1;
                        tm2 = v_wS1;
                        pos = pos1;
                        b = best1;
                        break;
                    case 2:
                        tm = wS2;
                        tm2 = v_wS2;
                        pos = pos2;
                        b = best2;
                        break;
                    case 3:
                        tm = wS3;
                        tm2 = v_wS3;
                        pos = pos3;
                        b = best3;
                        break;
                }
                if (pos == 1) {
                    if (tm.containsKey(org_word)) {
                        cnt = tm.get(org_word);
                    }
                    tm.put(org_word, cnt + 1);
                }
                if (tm2.containsKey(b)) {
                    cnt = tm2.get(b);
                }
                tm2.put(b, cnt + 1);

            }
        }
        try {
            StringBuilder sb = new StringBuilder();
            sb.append(stnce);
            sb.append("\t");
            sb.append(org_word);
            sb.append("\t");
            sb.append(best1);
            sb.append("\t");
            sb.append(pos1);
            sb.append("\t");
            sb.append(best2);
            sb.append("\t");
            sb.append(pos2);
            sb.append("\t");
            sb.append(best3);
            sb.append("\t");
            sb.append(pos3);
            sb.append("\t");
            sb.append(s);
            sb.append("\t");
            sb.append(t);
            sb.append("\n");
            evalLogFile.write(sb.toString());
            evalLogFile.flush();
        } catch (IOException e) {
            IOHelper.logError("(Evaluator.logEvaluation()) ERROR writing evalLog: " + e.getMessage());
        }

    }

    private void writeStatistic() {
        s_wS1.clear();
        s_wS2.clear();
        s_wS3.clear();
        s_wS1.putAll(wS1);
        s_wS2.putAll(wS2);
        s_wS3.putAll(wS3);

        try {
            for (String word : s_wS1.keySet()) {
                // <Wort> TAB <Anzahl Treffer> TAB <Gesamtanzahl Vorschläge (Treffer oder nicht)> TAB <Gesamtvorkommen d. Wortes>
                writeWordStatistic1.write(word + "\t" + wS1.get(word) + "\t" + v_wS1.get(word) + "\t" + w.get(word) + "\n");
                writeWordStatistic1.flush();
            }
            for (String word : s_wS2.keySet()) {
                writeWordStatistic2.write(word + "\t" + wS2.get(word) + "\t" + v_wS2.get(word) + "\t" + w.get(word) + "\n");
                writeWordStatistic2.flush();
            }
            for (String word : s_wS3.keySet()) {
                writeWordStatistic3.write(word + "\t" + wS3.get(word) + "\t" + v_wS3.get(word) + "\t" + w.get(word) + "\n");
                writeWordStatistic3.flush();
            }
            writeWordStatistic1.close();
            writeWordStatistic2.close();
            writeWordStatistic3.close();
        } catch (IOException ex) {
            IOHelper.logError("(Evaluator) Failed to write statistic: " + ex.getMessage());
        }
    }
}
